using System;
using System.Collections.Generic;
using System.Linq;
using Arcane.Source.Client;
using Arcane.Source.Client.Extensions;
using Godot;

namespace Arcane.Source.Scripting
{
    public unsafe class CommandRegistry<T>()
    {
        public delegate*<T, string, Variant> fpGet = null;
        private readonly Dictionary<string, Func<CommandRegistry<T>, Ast, Command<T>>> _macros = [];
        private readonly Dictionary<string, nuint> _fPtrs = [];

        public void RegisterMacro(string name, Func<CommandRegistry<T>, Ast, Command<T>> macro)
        {
            _macros.Add(name, macro);
        }

        public unsafe void Register(string name, delegate*<T, Variant[], Variant> fp)
        {
            _fPtrs.Add(name, (nuint)fp);
        }

        public unsafe delegate*<T, Variant[], Variant> Get(string name)
        {
            if (_fPtrs.TryGetValue(name, out var fp))
            {
                return (delegate*<T, Variant[], Variant>)fp;
            }
            return null;
        }

        public unsafe bool TryGetFP(string name, out delegate*<T, Variant[], Variant> fp)
        {
            if (_fPtrs.TryGetValue(name, out var ifp))
            {
                fp = (delegate*<T, Variant[], Variant>)ifp;
                return true;
            }
            fp = null;
            return false;
        }

        public ICommand<T> Compile(Ast ast)
        {
            switch (ast)
            {
                case Value val:
                    return new CmdParam<T>(val.value);
                case Ident ident:
                    unsafe
                    {
                        if (_macros.TryGetValue(ident.name, out var macro))
                        {
                            return macro(this, ast);
                        }
                        else if (TryGetFP(ident.name, out var fp))
                        {
                            return new Command<T>(fp, null);
                        }
                        else
                        {
                            return ident.name switch
                            {
                                "+" => new CmdOp<T>(&VariantExt.Add, null),
                                "-" => new CmdOp<T>(&VariantExt.Sub, null),
                                "*" => new CmdOp<T>(&VariantExt.Mul, null),
                                "/" => new CmdOp<T>(&VariantExt.Div, null),
                                "=" => new CmdOp<T>(&VariantExt.Eq, null),
                                "<" => new CmdOp<T>(&VariantExt.Lt, null),
                                ">" => new CmdOp<T>(&VariantExt.Gt, null),
                                "<=" => new CmdOp<T>(&VariantExt.Le, null),
                                ">=" => new CmdOp<T>(&VariantExt.Ge, null),
                                "!=" => new CmdOp<T>(&VariantExt.Ne, null),
                                "if" => new CmdIf<T>(),
                                _ => new CmdGet<T>(fpGet),
                            };
                        }
                    }
                case Compound compound:
                    var first = Compile(compound.asts.First());
                    switch (first)
                    {
                        case Command<T> cmd:
                            cmd.parameters = compound.asts.Skip(1).Select(Compile).ToArray();
                            return cmd;
                        case CmdOp<T> op:
                            op.operands = compound.asts.Skip(1).Select(Compile).ToArray();
                            return op;
                        case CmdIf<T> ifExpr:
                            {
                                var iter = compound.asts.Skip(1).Select(Compile);
                                ifExpr.cond = iter.First();
                                iter = iter.Skip(1);
                                ifExpr.then = iter.First();
                                iter = iter.Skip(1);
                                ifExpr.fin = iter.First();
                                return ifExpr;
                            }
                    }

                    return new CmdBlock<T>(compound.asts.Select(Compile).ToArray());
                case Quoted q:
                    return new CmdParam<T>(q.ast.ToVariant());
                case KVPair kv when kv.key is Ident key:
                    return new CmdKV<T>(key.name, Compile(kv.value));
                default:
                    throw new ParseException();
            }
        }
    }
}